﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Diagnostics;

namespace BReusable.StaticReflection
{
	/// <summary>
	/// From BReusable
	/// Avoid this for high performance sections
	/// http://blogs.msdn.com/alexj/archive/2008/03/03/maybe-there-is-more.aspx
	/// Also
	/// http://blogs.developpeur.org/miiitch/archive/2008/02/29/vendredi-c-est-expression-tree.aspx
	/// </summary>
	public static class MaybeWalker
	{

		private static readonly MethodInfo maybeShallowMethod;
		/// <summary>
		/// For types that return a nullable
		/// </summary>
		private static readonly MethodInfo maybeShallowNullableMethod;

		///// <summary>
		///// For types that return a primitive (not nullable)
		///// to convert them to nullable
		///// </summary>
		//private static readonly MethodInfo maybePrimitiveMethod;

		static MaybeWalker()
		{
			var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
			Expression<Func<object>> exp = () => MaybeWalker.MaybeShallow<object, object>(null, null);
			var name = Member.VariableName(exp);
			Debug.Assert(name.IsNullOrEmpty() == false);
			maybeShallowMethod = typeof(MaybeWalker).GetMethod(name, bindingFlags);
			Debug.Assert(maybeShallowMethod != null);
			Expression<Func<int?>> expNullable = () => MaybeWalker.MaybeShallowNullable<object, int?>(null, null);

			maybeShallowNullableMethod = typeof(MaybeWalker).GetMethod(Member.StaticMethodName(() => MaybeWalker
				.MaybeShallowNullable<object, Nullable<int>>(null, null)), bindingFlags);
			Debug.Assert(maybeShallowNullableMethod != null);
			//maybePrimitiveMethod = typeof(Walk).GetMethod(Member.Name(() => Walk.MaybeShallowPrimitive<object, int>(null, null)));
		}


		/// <summary>
		/// For expressions that return a nullable types
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="V">Nullable<></typeparam>
		/// <param name="t"></param>
		/// <param name="expression"></param>
		/// <returns></returns>
		private static V MaybeShallowNullable<T, V>(this T t, Expression<Func<T, V>> expression)
			where T : class
		//			where V:struct
		{
			if (typeof(V).IsNullable() == false)
				throw new ArgumentException(Member.StaticMethodName(() =>
					MaybeShallowNullable(string.Empty, x => -1)) + " is for nullable types");
			if (t == null)
				return default(V);
			return expression.Compile()(t);
		}


		public static TValue Maybe<T, TCurry, TValue>(this T t,TValue defaultValue, Expression<Func<T,TCurry>> expression,Func<TCurry, TValue> finalAccessor)
			where T : class
			where TValue : struct
		{
			var curry = t.Maybe(expression);
			if (curry != null)
				return finalAccessor(curry);
			else
				return defaultValue;
		}
		

		///// <summary>
		///// 
		///// </summary>
		///// <typeparam name="T"></typeparam>
		///// <typeparam name="V"></typeparam>
		///// <param name="t"></param>
		///// <param name="expression"></param>
		///// <returns></returns>
		//[Obsolete]
		//public static V? MaybeShallowPrimitive<T, V>(this T t, Expression<Func<T, V>> expression)
		//    where T:class
		//    where V:struct
		//{
		//    if (typeof(V).IsNullable())
		//        throw new ArgumentException(Member.StaticMethodName(()=>MaybeShallowPrimitive(string.Empty,x=>-1))+ " is not for nullables");
		//    if (t != null)
		//        return expression.Compile()(t);
		//    return new Nullable<V>();
		//}

		/// <summary>
		/// Goes as Deep as the expression for primitive return types like int
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TCurry"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="t"></param>
		/// <param name="expression"></param>
		/// <param name="finalAccessor"></param>
		/// <returns></returns>
		public static TResult? MaybeToNullable<T, TCurry, TResult>(this T t, Expression<Func<T, TCurry>> expression, Func<TCurry, TResult> finalAccessor)
			where T : class
			where TResult : struct
		{
			var curry = t.Maybe(expression);
			if (curry != null)
				return new Nullable<TResult>(finalAccessor(curry));
			return null;
		}

		public static ArgumentException MaybePrimitiveArgumentException()
		{
			return new ArgumentException(Member.StaticMethodName(() => Maybe(string.Empty, x => string.Empty)) + " does not support primitives");
		}

		public static TResult Maybe<T, TResult>(this T t, Expression<Func<T, TResult>> expression, TResult defaultValue)
			where T : class
			where TResult:class
		{
			return Maybe(t,expression) ?? defaultValue;
		}

		/// <summary>
		/// Goes as deep as the expression so x.Maybe<Z,X>( x=>x.y.z)
		/// Not for use with primitive return types
		/// Inspired from
		/// http://blogs.developpeur.org/miiitch/archive/2008/02/29/vendredi-c-est-expression-tree.aspx
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="V"></typeparam>
		/// <param name="t"></param>
		/// <param name="ex"></param>
		/// <returns></returns>
		public static TResult Maybe<T, TResult>(this T t, Expression<Func<T, TResult>> ex)
			where T : class
		{
			if (typeof(TResult).IsClass == false && typeof(TResult).IsNullable() == false)
				throw MaybePrimitiveArgumentException();
			// It handles only the case of the demo
			if (ex.Body is MemberExpression)
			{
				MethodCallExpression memberEx = ConvertMemberToMethodCall(ex.Body as MemberExpression);

				LambdaExpression lambda = Expression.Lambda(memberEx, new ParameterExpression[] { ex.Parameters[0] });

				//Changed from as V to support things that are not classes
				return (TResult)lambda.Compile().DynamicInvoke(new object[] { t });

			}

			else if (ex.Body is NewExpression)
			{
				NewExpression newExp = (NewExpression)ex.Body;

				return ex.Compile()(t);
			}
			else
			{
				throw new NotSupportedException("");
			}

		}

		/// <summary>
		/// Only goes one level in so x.MaybeShallow(x=>x.y)
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="V"></typeparam>
		/// <param name="t"></param>
		/// <param name="selector"></param>
		/// <returns></returns>
		private static V MaybeShallow<T, V>(this T t, Func<T, V> selector)
			where T : class
			where V : class
		{
			if (t == null) return null;
			return selector(t);
		}

		/// <summary>
		/// This method converts a call from a member in method call. Basically:
		/// '. MaProp' becomes '. Maybe (p => p.MaProp)'
		/// </summary>
		/// <param name="memberExpression"></param>
		/// <returns></returns>
		private static MethodCallExpression ConvertMemberToMethodCall(MemberExpression memberExpression)
		{
			Expression ex = null;

			// The recursive call is made here
			if (memberExpression.Expression is MemberExpression)
			{
				ex = ConvertMemberToMethodCall(memberExpression.Expression as MemberExpression);
			}
			else
			{
				ex = memberExpression.Expression;
			}

			// A Generic method retrieves the "Maybe"
			MethodInfo methodInfo = maybeShallowMethod;
			Type type = null;

			//default case is a property

			if (memberExpression.Member is PropertyInfo)
			{
				var prop = (PropertyInfo)memberExpression.Member;
				type = prop.PropertyType;
			}
			else
				if (memberExpression.Member is FieldInfo)
				{
					var field = (FieldInfo)memberExpression.Member;
					type = field.FieldType;
				}
				else
				{
					throw new NotImplementedException("");
				}

			if (type.IsNullable())
				methodInfo = maybeShallowNullableMethod.MakeGenericMethod(new Type[] { memberExpression.Member.DeclaringType, type });
			else if (type.IsPrimitive)
			{
				//methodInfo = maybePrimitiveMethod.MakeGenericMethod(new Type[] { memberExpression.Member.DeclaringType, type });
				throw new NotSupportedException("Primitives are not supported");
			}
			else //class
			{
				// Obligatory passage: get a version of the standard method "Maybe"
				methodInfo = methodInfo.MakeGenericMethod(
					new Type[] { memberExpression.Member.DeclaringType, type });
			}

			// Create a parameter to the lambda passed in parameter Maybe
			ParameterExpression p = Expression.Parameter(memberExpression.Member.DeclaringType, "p");

			// Create the lambda
			LambdaExpression maybeLamba = Expression.Lambda(
					Expression.MakeMemberAccess(p, memberExpression.Member),
					new ParameterExpression[] { p });

			// Create the call Maybe
			MethodCallExpression result = Expression.Call(
				null, methodInfo,
				new Expression[] { ex, maybeLamba });
			return result;
		}
	}
}



